/*
 * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.engine.test.api.history;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import java.util.List;
import org.activiti.engine.history.HistoricProcessInstanceQuery;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.Job;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.TimerJobQuery;

public class HistoricProcessInstanceQueryAndWithExceptionTest extends PluggableActivitiTestCase {

    private static final String PROCESS_DEFINITION_KEY_NO_EXCEPTION = "oneTaskProcess";
    private static final String PROCESS_DEFINITION_KEY_WITH_EXCEPTION_1 = "JobErrorCheck";
    private static final String PROCESS_DEFINITION_KEY_WITH_EXCEPTION_2 = "JobErrorDoubleCheck";

    private org.activiti.engine.repository.Deployment deployment;

    protected void setUp() throws Exception {
        super.setUp();
        deployment = repositoryService
            .createDeployment()
            .addClasspathResource("org/activiti/engine/test/api/runtime/oneTaskProcess.bpmn20.xml")
            .addClasspathResource("org/activiti/engine/test/api/runtime/JobErrorCheck.bpmn20.xml")
            .addClasspathResource("org/activiti/engine/test/api/runtime/JobErrorDoubleCheck.bpmn20.xml")
            .deploy();
    }

    protected void tearDown() throws Exception {
        repositoryService.deleteDeployment(deployment.getId(), true);
        super.tearDown();
    }

    public void testQueryWithException() throws InterruptedException {
        if (processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) {
            ProcessInstance processNoException = runtimeService.startProcessInstanceByKey(
                PROCESS_DEFINITION_KEY_NO_EXCEPTION
            );

            HistoricProcessInstanceQuery queryNoException = historyService.createHistoricProcessInstanceQuery();
            assertThat(queryNoException.count()).isEqualTo(1);
            assertThat(queryNoException.list()).hasSize(1);
            assertThat(queryNoException.list().get(0).getId()).isEqualTo(processNoException.getId());

            HistoricProcessInstanceQuery queryWithException = historyService.createHistoricProcessInstanceQuery();
            assertThat(queryWithException.withJobException().count()).isEqualTo(0);
            assertThat(queryWithException.withJobException().list()).hasSize(0);

            ProcessInstance processWithException1 = startProcessInstanceWithFailingJob(
                PROCESS_DEFINITION_KEY_WITH_EXCEPTION_1
            );
            TimerJobQuery jobQuery1 = managementService
                .createTimerJobQuery()
                .processInstanceId(processWithException1.getId());
            assertThat(jobQuery1.withException().count()).isEqualTo(1);
            assertThat(jobQuery1.withException().list()).hasSize(1);
            assertThat(queryWithException.withJobException().count()).isEqualTo(1);
            assertThat(queryWithException.withJobException().list()).hasSize(1);
            assertThat(queryWithException.withJobException().list().get(0).getId()).isEqualTo(
                processWithException1.getId()
            );

            ProcessInstance processWithException2 = startProcessInstanceWithFailingJob(
                PROCESS_DEFINITION_KEY_WITH_EXCEPTION_2
            );
            TimerJobQuery jobQuery2 = managementService
                .createTimerJobQuery()
                .processInstanceId(processWithException2.getId());
            assertThat(jobQuery2.withException().count()).isEqualTo(2);
            assertThat(jobQuery2.withException().list()).hasSize(2);

            assertThat(queryWithException.withJobException().count()).isEqualTo(2);
            assertThat(queryWithException.withJobException().list()).hasSize(2);
            assertThat(
                queryWithException
                    .withJobException()
                    .processDefinitionKey(PROCESS_DEFINITION_KEY_WITH_EXCEPTION_1)
                    .list()
                    .get(0)
                    .getId()
            ).isEqualTo(processWithException1.getId());
            assertThat(
                queryWithException
                    .withJobException()
                    .processDefinitionKey(PROCESS_DEFINITION_KEY_WITH_EXCEPTION_2)
                    .list()
                    .get(0)
                    .getId()
            ).isEqualTo(processWithException2.getId());
        }
    }

    private ProcessInstance startProcessInstanceWithFailingJob(String processInstanceByKey) {
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processInstanceByKey);

        List<Job> jobList = managementService.createJobQuery().processInstanceId(processInstance.getId()).list();

        for (Job job : jobList) {
            assertThatExceptionOfType(RuntimeException.class)
                .as("RuntimeException")
                .isThrownBy(() -> managementService.executeJob(job.getId()));
        }
        return processInstance;
    }
}
